#ifndef WKT_JSI_HOST_OBJECT_H
#define WKT_JSI_HOST_OBJECT_H
#pragma once

#include <jsi/jsi.h>

#include "WKTRuntimeAwareCache.h"

#include <functional>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#define STR_CAT_NX(A, B) A##B
#define STR_CAT(A, B) STR_CAT_NX(A, B)
#define STR_GET get_
#define STR_SET set_

/* *
 * Creates a new Host function declaration as a lambda with all deps passed
 * with implicit lambda capture clause
 */
#define JSI_HOST_FUNCTION_LAMBDA \
    [=](jsi::Runtime & runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value

/* *
 * Creates a new Host function declaration
 */
#define JSI_HOST_FUNCTION(NAME) \
    jsi::Value NAME(jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count)

/* *
 * Creates a new property setter function declaration
 */
#define JSI_PROPERTY_SET(NAME) void STR_CAT(STR_SET, NAME)(jsi::Runtime & runtime, const jsi::Value &value)

/* *
 * Creates a new property getter function declaration
 */
#define JSI_PROPERTY_GET(NAME) jsi::Value STR_CAT(STR_GET, NAME)(jsi::Runtime & runtime)

/* *
 * Creates a JSI export function declaration
 */
#define JSI_EXPORT_FUNC(CLASS, FUNCTION)                                                              \
    {                                                                                                 \
        #FUNCTION, (jsi::Value(JsiHostObject::*)(jsi::Runtime & runtime, const jsi::Value &thisValue, \
            const jsi::Value *arguments, size_t)) &                                                   \
            CLASS::FUNCTION                                                                           \
    }

/* *
 * Creates a JSI export function declaration with a specific name
 */
#define JSI_EXPORT_FUNC_NAMED(CLASS, FUNCTION, NAME)                                              \
    {                                                                                             \
        #NAME, (jsi::Value(JsiHostObject::*)(jsi::Runtime & runtime, const jsi::Value &thisValue, \
            const jsi::Value *arguments, size_t)) &                                               \
            CLASS::FUNCTION                                                                       \
    }

/* *
 * Creates a JSI export functions statement
 */
#define JSI_EXPORT_FUNCTIONS(...)                                      \
    const RNWorklet::JsiFunctionMap &getExportedFunctionMap() override \
    {                                                                  \
        static RNWorklet::JsiFunctionMap map = { __VA_ARGS__ };        \
        return map;                                                    \
    }

/* *
 * Creates a JSI export getter declaration
 */
#define JSI_EXPORT_PROP_GET(CLASS, FUNCTION)                                                                  \
    {                                                                                                         \
        #FUNCTION, (jsi::Value(JsiHostObject::*)(jsi::Runtime & runtime)) & CLASS::STR_CAT(STR_GET, FUNCTION) \
    }

/* *
 * Creates a JSI export getters statement
 */
#define JSI_EXPORT_PROPERTY_GETTERS(...)                                             \
    const RNWorklet::JsiPropertyGettersMap &getExportedPropertyGettersMap() override \
    {                                                                                \
        static RNWorklet::JsiPropertyGettersMap map = { __VA_ARGS__ };               \
        return map;                                                                  \
    }

/* *
 * Creates a JSI export setter declaration
 */
#define JSI_EXPORT_PROP_SET(CLASS, FUNCTION)                                                                          \
    {                                                                                                                 \
        #FUNCTION,                                                                                                    \
            (void (JsiHostObject::*)(jsi::Runtime & runtime, const jsi::Value &)) & CLASS::STR_CAT(STR_SET, FUNCTION) \
    }

/* *
 * Creates a JSI export setters statement
 */
#define JSI_EXPORT_PROPERTY_SETTERS(...)                                             \
    const RNWorklet::JsiPropertySettersMap &getExportedPropertySettersMap() override \
    {                                                                                \
        static RNWorklet::JsiPropertySettersMap map = { __VA_ARGS__ };               \
        return map;                                                                  \
    }

namespace RNWorklet {
namespace jsi = facebook::jsi;

using JsPropertyType = struct {
    std::function<jsi::Value(jsi::Runtime &)> get;
    std::function<void(jsi::Runtime &, const jsi::Value &)> set;
};

class JsiHostObject;

using JsiFunctionMap = std::unordered_map<std::string,
    jsi::Value (JsiHostObject::*)(jsi::Runtime &, const jsi::Value &, const jsi::Value *, size_t)>;

using JsiPropertyGettersMap = std::unordered_map<std::string, jsi::Value (JsiHostObject::*)(jsi::Runtime &)>;

using JsiPropertySettersMap =
    std::unordered_map<std::string, void (JsiHostObject::*)(jsi::Runtime &, const jsi::Value &)>;

/* *
 * Base class for jsi host objects
 */
class JsiHostObject : public jsi::HostObject {
public:
    JsiHostObject();
    ~JsiHostObject();

protected:
    /* *
     Override to return map of name/functions
     */
    virtual const RNWorklet::JsiFunctionMap &getExportedFunctionMap()
    {
        static const RNWorklet::JsiFunctionMap empty;
        return empty;
    }

    /* *
     Override to get property getters map of name/functions
     */
    virtual const JsiPropertyGettersMap &getExportedPropertyGettersMap()
    {
        static const JsiPropertyGettersMap empty;
        return empty;
    }

    /* *
     Override to get property setters map of name/functions
     */
    virtual const JsiPropertySettersMap &getExportedPropertySettersMap()
    {
        static const JsiPropertySettersMap empty;
        return empty;
    }

    /* *
     * Overridden jsi::HostObject set property method
     * @param rt Runtime
     * @param name Name of value to set
     * @param value Value to set
     */
    void set(jsi::Runtime &rt, const jsi::PropNameID &name, const jsi::Value &value) override;

    /* *
     * Overridden jsi::HostObject get property method. Returns functions from
     * the map of functions.
     * @param runtime Runtime
     * @param name Name of value to get
     * @return Value
     */
    jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;

    /* *
     * Overridden getPropertyNames from jsi::HostObject. Returns all keys in the
     * function and property maps
     * @param runtime Runtime
     * @return List of property names
     */
    std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &runtime) override;

private:
    RuntimeAwareCache<std::map<std::string, jsi::Function>> _hostFunctionCache;
};
} // namespace RNWorklet

#endif // WKT_JSI_HOST_OBJECT_H
